
class ZPack {
    scriptContainer: HTMLElement;
    modules: { [id: string]: Module } = {};
    files: { [filename: string]: Module[] } = {};
    viewContainer: HTMLElement;
    require: ZPackRequire = _require;

    constructor() {
        this.scriptContainer = document.createElement('div');
        this.scriptContainer.id = 'zpackScriptContainer';
        document.body.appendChild(this.scriptContainer);

        let viewContainer = document.getElementById('react');
        if (viewContainer == null) {
            viewContainer = document.createElement('div');
            viewContainer.id = 'react';
            document.body.appendChild(viewContainer);
        }
        this.viewContainer = viewContainer;
    }

    getFileName() {
        let script = document.currentScript as HTMLScriptElement | undefined;
        if (script && script.className === 'zpackScript' && script.src != null) {
            return script.src;
        } else {
            throw new Error('getDynamicId failed');
        }
    }

    defineExternal(id: string, code: string) {
        let module = new Module(id, { id, dependency: {} }, (rquire, exports, module) => module.exports = eval(code));
        module.resolved = true;
        this.register(module);
    }

    async fetchFile(filename: string) {
        let file = this.files[filename];
        if (file) {
            return;
        }
        file = this.files[filename] = [];
        return new Promise((resolve, reject) => {
            let script = document.createElement('script');
            script.className = 'zpackScript';
            script.src = filename;
            script.onerror = reject;
            script.onload = resolve;
            this.scriptContainer.appendChild(script);
        });
    }

    define(opt: DefineOptions, body: DefineBody) {
        if (this.modules[opt.id]) {
            console.warn('module ' + opt.id + 'duplicate!');
        }
        this.register(new Module(this.getFileName(), opt, body));
    }

    register(module: Module) {
        let file = this.files[module.filename];
        this.modules[module.id] = module;
        if (file) {
            file.push(module);
        }
    }

    async resolve(filename: string) {
        let module = this.modules[filename];
        if (module == null) {
            await this.fetchFile(filename);
            let id = this.require.resolve(filename);
            module = this.modules[id];
        }
        return module;
    }

    async doLoad(opt: any) {
        if (opt.external) {
            for (let i in opt.external) {
                this.defineExternal(i, opt.external[i]);
            }
        }
        await Promise.all((opt.preload || []).map((s: string) => this.resolve(s)));
        let React = await this.require.dynamic('react');
        let ReactDOM = await this.require.dynamic('react-dom');
        let Comp = await this.require.dynamic(opt.entry);
        if (Comp.__esModule) {
            Comp = Comp.default;
        }
        let comp = React.createElement(Comp);
        ReactDOM.render(comp, this.viewContainer);
        return 'ZPack Loaded!';
    }

    load(opt: any) {
        this.doLoad(opt).then(console.log).catch(console.error);

    }
}